using System;
using System.Collections.Generic;
using System.Text;
using SharpNeatLib.Evolution;
using SharpNeatLib.NeuralNetwork;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
using System.IO;

namespace ParaSharpNeatLib
{
    public class DistributedPopulationEvaluator : IPopulationEvaluator
    {
        //Each worker node has 1 RemoteNetworkEvaluator
        protected List<RemoteNetworkEvaluator> networkEvaluatorList = new List<RemoteNetworkEvaluator>();

        //Need a delegate to each worker node's evaluation method
        protected delegate AsyncEvaluation EvaluateDelegate(int[] id, List<INetwork> networks, double MIN_GENOME_FITNESS);
        protected List<EvaluateDelegate> runEvaluateDelegate = new List<EvaluateDelegate>();
        protected List<AsyncEvaluation> evals = new List<AsyncEvaluation>();

        protected IActivationFunction activationFn;
        protected long evaluationCount = 0;

        protected Population population;

        #region Constructors
        public DistributedPopulationEvaluator(IActivationFunction activationFn, List<string> nodes)
        {
            this.activationFn = activationFn;

            //Startup the population server
            ChannelServices.RegisterChannel(new TcpChannel(), false);

            //Get a reference to each worker node's network evaluator
            foreach (string node in nodes)
            {
                RemoteNetworkEvaluator rne;
                rne = (RemoteNetworkEvaluator)Activator.GetObject(
                        typeof(RemoteNetworkEvaluator), "tcp://" + node + ":51050/RemoteNetworkEvaluator");
                
                //Add to the list of workers
                networkEvaluatorList.Add(rne);

                //Create a delegate which allows us to call the evaluation method asynchronously
                AsyncEvaluation eval = new AsyncEvaluation(rne);
                evals.Add(eval);
                runEvaluateDelegate.Add(new EvaluateDelegate(eval.Evaluate));
            }
        }
        #endregion

        #region IPopulationEvaluator Members

        public bool BestIsIntermediateChampion
        {
            //just returns false by default. Override this with a custom version if you want.
            get { return false; }
        }

        public void EvaluatePopulation(Population pop, EvolutionAlgorithm ea)
        {
            population = pop;

            int count = pop.GenomeList.Count;
            
            // A list of all networks in the population
            List<INetwork> networks = new List<INetwork>();

            // Create phenotypes for all the individuals
            // This is done here for 2 reasons:
            //    1. Co-Evolution: it may be required for workers to
            //         have global access to all individuals in order 
            //         to compete them against each other.
            //    2. Batching: rather than send each individual to a worker
            //         node one-by-one, we want to send one lump of individuals
            //         so as to reduce communication overhead.
            foreach (IGenome genome in pop.GenomeList)
                networks.Add(genome.Decode(activationFn));

            // Give all co-evolution evaluators a copy of the entire population.
            // We could save a little performance time by just checking one and assuming
            // they all are if one is, by who knows what kind of crazy evaluators people
            // may end up using.
            foreach (RemoteNetworkEvaluator rne in networkEvaluatorList)
                if (rne.Coevolution)
                    rne.Networks = networks;

            // Determine how many networks each worker node will evaluate
            int individualsPerWorker = networks.Count / networkEvaluatorList.Count;
            
            // The starting point of the range for each worker to evaluate
            int begin = 0;

            // The number of extra individuals to be evaluated
            // (necessary if the number of individuals is not divisible by
            //  the number of workers)
            int extraIndividual = networks.Count % networkEvaluatorList.Count;
            
            for (int i = 0; i < networkEvaluatorList.Count; i++)
            {
                //calculate the end boundary for this worker to evaluate
                int end = begin + individualsPerWorker;
                
                //do we need to evaluate an extra individual
                if (i < extraIndividual)
                    end++;

                // Get the list of networks for this worker to evaluate
                List<INetwork> toEvaluate = networks.GetRange(begin, end - begin);

                //the ids of the individuals that this network will be evaluating
                int[] ids = new int[end - begin];
                for (int j = 0; begin < end; j++, begin++)
                    ids[j] = begin;

                lock (runEvaluateDelegate)
                {
                    // Evaluate the networks asynchronously
                    runEvaluateDelegate[i].BeginInvoke(ids, toEvaluate, EvolutionAlgorithm.MIN_GENOME_FITNESS,
                    new AsyncCallback(EvaluationComplete), null);
                }
            }
            
            // Block until all evaluations are completed
            while (System.Threading.Interlocked.Read(ref evaluationCount) < count)
            {
                // 1 second seems reasonable.  If that hurts performance too much, then
                // you probably don't need to be using a distributed evaluator to begin with!
                System.Threading.Thread.Sleep(1000);
            }
        }

        public ulong EvaluationCount
        {
            get { return (ulong)evaluationCount; }
        }

        public string EvaluatorStateMessage
        {
            // Returns the number of evaluations finished
            get { return "Evaluations performed: " + System.Threading.Interlocked.Read(ref evaluationCount); }
        }

        public bool SearchCompleted
        {
            //not supported in current SharpNEAT, so we don't support it either.
            get { return false; }
        }

        #endregion

        /// <summary>
        /// This method will be invoked asynchronously for each completed
        /// worker (batched) evaluation.
        /// </summary>
        /// <param name="ar">The results of the evaluations.</param>
        private void EvaluationComplete(IAsyncResult ar)
        {
            AsyncResult result = (AsyncResult)ar;
            EvaluateDelegate del = (EvaluateDelegate)result.AsyncDelegate;
            AsyncEvaluation eval = del.EndInvoke(ar);

            // We need to safely access the population, so obtain a lock
            IGenome genome;
            lock (population)
            {
                for (int i = 0; i < eval.IDs.Length; i++)
                {
                    genome = population.GenomeList[eval.IDs[i]];
                    genome.Fitness = eval.Fitness[i];

                    // Reset these genome level statistics.
                    genome.TotalFitness += genome.Fitness;
                    genome.EvaluationCount++;
                }
            }
            
            // Update master evaluation counter.
            System.Threading.Interlocked.Add(ref evaluationCount, eval.IDs.Length);
        }
    }
}
